Windows '95 and Flat Thunking Contents ======== Overview of Win32/Win32s thunking mechanisms The flat thunking mechanism A flat thunking example Debugging notes Recommended reading Overview of Win32/Win32s thunking mechanisms ============================================ For the Win32s, Windows NT and Windows 95 platforms, Microsoft has provided us with three thunking mechanisms: universal thunking, general thunking, and flat thunking. Universal thunking is available to Win32 applications running on Win32s, a 32-bit translation layer which allows 32-bit programs to be run on 16-bit Windows 3.x. Primarily, this mechanism accomodates 32-bit applications which are running in a hybrid environment of 32-bit modules inherent to the application itself and 16-bit modules inherent to the 16-bit environment. General thunking is available on both Windows NT and Windows 95. Howevever, it is intended for use primarily on Windows NT and Microsoft documentation advises that applications using General Thunking should not be expected to have the same behaviour when run under Windows NT and Windows 95 due to differences in the operating systems. General thunking allows a 16-bit application to call into a 32-bit DLL, but not the reverse. Its usefulness seems to be primarily to support incremental porting of a 16-bit application to 32 bits, where the DLLs of the application would be ported first, and the executable only in a later revision, after all DLLs have been ported. Flat thunking is available only under Windows 95. Flat thunking allows bi-directional calls between 16- and 32-bit modules. Using flat thunking requires that 16- and 32-bit modules be rebuilt, linking in special thunk modules created using the Microsoft thunk compiler and an assembler. Like general thunking, it seems primarily designed to support incremental porting of applications. An advantage of flat thunking is that you can call in either direction, and can even have a single call chain that moves back and forth between a pair of 16- and 32-bit modules. However, a module can only connect to one other module, so a bit of planning may be wise for larger projects. The flat thunking mechanism =========================== Flat thunking requires that we create a special assembly module, assemble it twice to a 16-bit and a 32-bit object, then link those two objects into the corresponding 16- and 32-bit modules (DLLs or executables). The assembly file is created by first writing a thunk script, which is very much like a C header file containing function prototypes and type definitions. The Microsoft thunk compiler then translates the script into a single assembly source file containing both 16- and 32-bit code. Two assembler macros, IS_16 and IS_32, allow the file to be assembled to the two distinct objects. Next, code is added to the 16- and 32-bit modules (DLL's or executables) to allow a runtime connection to be established between the two modules. Finally, a few modifications are made to the module definition files for each of the targets and the targets are brought up to date. To examine the flat thunking mechanism, we will use an example of a 32-bit DLL calling a function in a 16-bit DLL, which is what the example "Thunker" does. In the function names referred to below, the "ThunkObj_" prefix is a result of names generated by the Microsoft thunk compiler which it derives from the filename of our thunk script. These are not standard names that you can look up in a help file. First, the 32-bit DLL must establish a connection with the 16-bit DLL. This is done by calling ThunkObj_ThunkConnect32 and is most easily done from the 32-bit DLL's DllEntryPoint. This call causes the 16-bit DLL to be loaded. If the 16-bit DLL is marked for Windows 4.0, then the system will call its DllEntryPoint. A quirk is that the 16-bit DLL must have both a LIbMain and a DllEntryPoint. LibMain is called only when the DLL is loaded, while DllEntryPoint is called both on load and unload. In DllEntryPoint, the 16-bit DLL establishes its side of the connection with the 32-bit DLL by calling ThunkObj_ThunkConnect16. The process of establishing this connection invokes code in the thunk modules which initializes jump tables for the exported functions. In the thunker example, the 16-bit DLL does not call into the 32-bit DLL, so the code generated for the 16-bit thunk module contains code only to initialize this jump table, after which, the 16-bit thunk module's job is done. Calls can now be made between the 32- and 16-bit DLL's. Given that the 16-bit DLL exports a function Fun16, a call from the 32-bit DLL can be described. First, the call from the 32-bit DLL is actually resolved to a Fun16 procedure in the ThunkObj assembly module (which is why we never do anything to import Func16 from the 16-bit DLL). This thunk procedure fixes up the stack to account for a change from standard call to pascal calling convention, accomodates changes in data sizes (ie, size of int), and translates pointers (between 32-bit flat pointers expected in the 32-bit module and the selector:offset format expected in the 16-bit module). The thunk then uses the jump table to call directly into the 16-bit DLL. The connection is broken by calling the same ThunkObj_ThunkConnect32 and ThunkObj_ThunkConnect16 functions, but passing DLL_PROCESS_DETACH as the last parameter. A flat thunking example ======================= The example, Thunker, provides a shell demonstration of flat thunking. In this example, a 32-bit application uses a 32-bit DLL which calls into a 16-bit DLL. Microsoft documentation recommends that we thunk between DLL's rather than directly from an executable to a DLL as we can later port the 16-bit DLL and then simply replace the 16- and 32-bit thunking DLL's with one new 32-bit DLL. Since flat thunking has the limitation of a module being able to connect to only one other module, it is the only way to have a 32-bit executable effectively use multiple 16-bit DLL's, or vice-versa. Also, the model of interfacing thru a DLL is conducive to a multi-platform application which will determine at runtime which interface DLL to use (since no one thunking mechanism will work on all Windows 32-bit platforms). The example demonstrates all the basic requirements for coding and building an application using flat thunking. I strongly recommend you read thru the makefile and all the associated source - including the module definition files. Omitting a step or requirement can lead to strange runtime failures that can be difficult to debug. Further information on flat thunking is available in the Win32 online help file. Go to index item Thunk Compiler and page thru the "chapter". Further information on general and universal thunking is available in the Microsoft Win32 SDK documentation, and on the Microsoft Developer's Network. This example demonstrates calling from a 32-bit application into a 16-bit DLL. Per recommendations in Microsoft technical documentation, this is done by calling thru a 32-bit DLL. The example presumes that all calls into the 16-bit DLL are made only from the 32-bit DLL and describes modifications from that starting point. The application model looks like this: ÚÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄ¿ ³ 32.exe ³ÄÄÄ>³ 32.dll ³ ³ 16.dll ³ ÀÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÄÁÄÄÄÄ¿ ÚÄÄÄÁÄÄÄÄÄÄ¿ ³ ÀÄÄ´ to32.obj ³ÄÄÄÄ>³ to16.obj ÃÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ At runtime, the executable causes the 32-bit DLL to be loaded and that DLL establishes a connection by calling a Thunk_Connect function exported from the 16-bit thunk object. The 16-bit side then initializes a jump table which is passed back to the 32-bit DLL. When a call is made to a function exported from the 16-bit DLL, the call from the 32-bit DLL call actually resolves to a function in the 32-bit thunk object. This function then performs the steps neccassary to translate the call (ie, allowing for different sizes in data types) and then calls thru the jump table to the actual function in the 16-bit DLL. On return, control returns to the thunk function in the 32-bit thunk object, which does some translating of return values and structures passed by reference and ultimately returns to the original caller. To implement a thunking application, you must: 1) Create a thunk script providing declarative information about functions and data structures being thunked. 2) Generate assembly source from the thunk script using the Microsoft thunk compiler. 3) Assemble the thunk module to a 16-bit object module which will be linked into the 16-bit DLL exporting the user functions. 4) Assemble the thunk module to a 32-bit object module which will be linked into the 32-bit DLL that will be importing the user functions. 5) Modify the 16-bit DLL to provide a DllEntryPoint function which will be called to establish a connection with the 32-bit DLL. 6) Modify the 16-bit DLL's module definition file to: a) Add SUBSYSTEM 4.0 so the DLL is marked for Windows 4.0; b) Export DllEntryPoint, the 16-bit thunk data, and those functions you wish to call from the 32-bit world; and, c) Import C16THKSL01 and THUNKCONNECT16 from kernel. 7) Modify the 32-bit DLL to establish and break the connection with the 16-bit DLL at runtime. 8) Modify the 32-bit DLL's module definition file to: a) import ThunkConnect32@24 from kernel32 b) export the 32-bit thunk data. Special notes: The 16-bit DLL must be marked for subsystem 4.0, else the added DllEntryPoint function will not be called to establish the connection. This can be done with the linker /V switch or with a SUBSYSTEM entry in the module definition file. Of course, having done this, the DLL will only work on Windows 95. When assembling the thunk module, you must use the assembler's /ml option with /DIS_32 and must not use it with /DIS_16. This is because the thunk compiler always generates case sensitive symbols. By default, the 32-bit side of the thunk mechanism uses standard call calling convention, for which the case sensitivity must be preserved. The 16-bit side of the thunk mechanism uses pascal calling convention and the symbols there should be forced to upper case. (Since the thunk compiler generates functions which manipulate parameters being passed between the 16- and 32-bit modules, do not attempt to override the default calling conventions.) You may notice that in the 32-bit thunk module, the generated names for functions are created by adding to the function name an at sign ('@') followed by a numeric value representing the width of the parameter list. This is the naming convention that Microsoft uses for stdcall calling convention and the names are generated by the Microsoft thunk compiler. To resolve them, Turbo Assembler, when given the -utthk switch, automatically generates aliases which match the Borland naming convention. You can examine both forms of these names using tdump with this syntax: tdump -oipubdef -oiextdef -oialias thkobj16.obj tdump -oipubdef -oiextdef -oialias thkobj32.obj The 32-bit module can be built with debug info, but the 16-bit module cannot as tasm32 generates only 32-bit debug info which the 16-bit linker does not expect. Also, the -utthk switch is unique to tasm32 and is rejected by tasm and tasmx. Debugging notes =============== Turbo Debugger for Win32 cannot step from 32-bit code into 16-bit code. It is possible to step thru the 32-bit thunk module in source assembly or in the debuggers CPU view. While in the CPU view, do not attempt to step into a call thru the thunk table into the 16-bit DLL as it will cause TD32 to hang. Attempting to debug with available tool sets at this level will likely be very challenging and require very good Assembly language skills. Recommended reading =================== Additional information on thunking under various Microsoft Windows' environments can be found on the Microsoft Developers Network CD and in the Microsoft Win32 SDK documentation. Of particular interst are the articles "Flat Thunking Mechanics" and "How to Debug Flat Thunks" on the MSDN CD.